import {Client, ClientChannel} from 'ssh2';
import fs from "fs";
import { FileType } from './IVsshStatus';
import client from 'scp2';
import { Tab } from './ui/core/TabBar';
import IParser from './parser/IParser';
import Program from './ConnectPool';
import Status from './StatusManager';
/**
 * 我决定为每个任务单独建立一个连接。建立连接在外面建吧。
 * v1.0
 */
export default class Connect{
    private stream:ClientChannel;
    private conn:Client;
    private dataLock="";
    private parser:IParser
    // private mainProgram:Program
    /**
     * 一个连接的封装
     * @param stream 连接的流
     * @param nparser 创建渲染器，临时这么写。
     */
    constructor(conf:ConnectInnerConfig){
        // this.mainProgram=conf.mainProgram;
        this.stream=conf.stream
         this.conn=conf.conn;

        this.parser=conf.parserCreator(this)
        this.setExpect(Status.getExpect()).then(async ()=>{
            // this.sendTest();
            // await this.init();
            setTimeout(()=>{
                this.parser.onConnected()
            },100);
        })
    }

    /**
     * 把登录信息放走
     * @returns 
     * @deprecated
     */
    async init(){
        return new Promise<void>((reslove,reject)=>{
            const wait=(data: Buffer)=>{
                const a=data.toString();
                console.log(a);
                if(a.trim().indexOf(Status.getExpect())>=0){
                    this.dataLock=""
                    reslove();
                }else{
                    this.conn.once("data",wait)
                }
            }
            this.conn.once("data",wait)
        })
    }

    /**
     * ssh是一收一发的，所以我需要验证一下是否接收完成。
     */
    public async checkCommandEnd(){
        
        return new Promise<boolean>((resovlve,reject)=>{
            let verifyCode=`checkEnd${Math.random()}`;
            this.stream.write(`echo ${verifyCode+"\n"}`);
            let onData=(data: Buffer)=>{
                let rel=data.toString();
                if(data.toString().trim().indexOf(verifyCode)>=0){
                    resovlve(true)
                }else{
                    this.stream.once('data',onData)
                }
            }
            this.stream.once('data',onData)
        })
    }
    
    /**
     * @deprecated
     * 获取上一条命令的退出代码。
     * @returns 0为正常，127为找不到命令
     */
    async getLastExitCode(){
        let exitCode=(await this.exec("echo $?")).data.trim();
        // 临时解决返回带命令的问题。这个根治要从exec方法中解决。
        let el=exitCode.trim().split('\n');
        exitCode=el[el.length-1];
        console.warn(`退出代码测试${exitCode}`);
        return Number(exitCode);
    }

    // /**
    // }


    /**
     * 测试连接
     * @param config 
     */
    static async testConnect(conf:BaseConnectConfig){
        return await new Promise((resolve:(ok:boolean)=>void,reject)=>{
            let seed=Math.random();
            const conn = new Client();
            conn.on('ready', () => {
                resolve(true);
            }).connect({
                host: conf.host,
                port: conf.port,
                username:conf.username,
                privateKey:conf.privateKey,
                password:conf.password,
                readyTimeout:150000,
                
            })
            conn.on('error',(e)=>{
                //写进try是为了单元测试
                try{
                    alert("连接失败！");
                }catch(e){}
                reject(e);
            });
        })
    }

    /**
     * 创建一个连接
     * @param conf 
     * @returns 一个连接的实例
     */
    static async create(conf:ConnectConfig){
        return await new Promise((resolve:(stream:Connect)=>void,reject)=>{
            let seed=Math.random();
            const conn = new Client();
            conn.on('connect',console.error);
            conn.on('change password',console.error);
            conn.on('banner',console.error);
            conn.on('continue',console.error);
            conn.on('end',console.error);
            conn.on('greeting',console.error);
            conn.on('keyboard-interactive',console.error);
            conn.on('tcp connection',console.error);
            conn.on('timeout',console.error);
            conn.on('x11',console.error);
            conn.on('ready', () => {
                // console.log('Client :: ready');
                Status.setHost(conf.host);
                Status.setPort(conf.port);
                Status.setUsername(conf.username)
                Status.setPrivateKey(conf.privateKey);
                Status.setPassword(conf.password)
                // conn.end();
                conn.shell((err, stream) => {
                    if (err) reject(err);
                    // 有些过长的命令会被换行。。。
                    stream.setWindow(256,1024,2048,8192);
                    stream.on('close', () => {
                        console.log('Stream :: close');
                        conn.end();
                    }).on('end',()=>{
                        console.log("数据传输完毕");
                    });
                    stream.on("data",(data:Buffer)=>{
                        fs.mkdir("./logs/",{recursive:true},(err,path)=>{
                            if(err){
                                console.error(err);
                            }
                            fs.writeFile(`./logs/${conf.username}@${conf.host}_seed_${seed}.log`,data,{flag:"a"},()=>{});
                        })
                        
                    })
                    resolve(new Connect({stream,
                        conn,
                        // mainProgram:conf.mainProgram,
                        parserCreator:conf.parserCreator
                    }));
                    
                });
            }).connect({
                host: conf.host,
                port: conf.port,
                username:conf.username,
                privateKey:conf.privateKey,
                password:conf.password,
                readyTimeout:150000,
                
            })
            conn.on('error',(e)=>{
                //写进try是为了单元测试
                try{
                    alert("连接失败！");
                }catch(e){}
                console.error(e);
                fs.writeFile(`./err.log`,`${e.message}\n${e.stack}`,{flag:"a"},()=>{});
                reject(e);
            });
        })
    }

    /**
     * 执行一个shell 命令
     * 这个方法实现起来蛮复杂的。
     * 我遇到过服务端以“添油战术”返回内容的，也见过一次性返回全部内容的。
     * ssh也没个表示传输开始和结束的东西，需要抗得干扰蛮多的。
     * @param cmd 要执行的命令
     * @param onInfo 当收到消息时的回调
     * @returns 命令完整的返回会出现在resolve的字符串中
     */
     async exec(cmd:string,onInfo:(str:string)=>void=()=>{}){
        let sendCmd=`${cmd.trim()};echo VisualSSHStatus:$?\n`;
        const lockid=await this.waitDataLockReleaseAndCreateLock();
        return await new Promise((resolve:(v:SSHResult)=>void,reject)=>{
            this.stream.write(sendCmd);
            
            let rel="";
            let onData=(data: Buffer)=>{
                let hasCmd = data.toString().indexOf(sendCmd.trim());
                let hasTatile=data.toString().indexOf(Status.getExpect());
                // console.log("%c 接收到的数据","color:green",data.toString());
                // 输出命令行，一定是开始
                if(hasCmd<0){
                    onInfo(data.toString());
                }
                // 有这个 标志，一定是结束
                // 但是也有可能把之前的命令行
                if(hasTatile>=0){
                    this.releaseDataLock(lockid);
                    rel +=data.toString().substr(0,data.toString().indexOf(Status.getExpect(),-1))
                    const fullrel=rel;
                    rel=rel.replace(sendCmd.trim(),"");
                    let relArr=rel.split("VisualSSHStatus:")
                    if(relArr.length<2){
                        console.error(relArr);
                        throw new Error(`${cmd}:我在找这个影响系统稳定性的问题,rel:${fullrel}`);
                    }
                    
                    let sshResult:SSHResult={
                        cmd,
                        data: relArr[relArr.length-2].trim(),
                        exitCode: Number(relArr[relArr.length-1])
                    }
                    // console.log("返回内容",rel);
                    if(sshResult.exitCode!==0){
                        console.warn(`警告：命令${cmd}异常退出。退出码${relArr[1]}`)
                    }
                    if(sshResult.data.trim()=="$?"){
                        console.error(rel);
                        throw new Error("加载错误！！！");
                    }
                    resolve(sshResult)
                    return ;
                }
                
                rel=rel+data.toString();
                // console.log("来数据"+data.toString());
                this.stream.once('data', onData);
            }
            this.stream.once('data', onData);
            // this.conn.connect({
            //     host: Status.getHost(),
            //     port: parseInt(<string>Status.getPort()),
            //     username:Status.getUsername(),
            //     privateKey:Status.getPrivateKey(),
            //     password:Status.getPassword(),
            //     readyTimeout:150000,
                
            // })
            // this.conn.on('ready', () => {
            //     this.conn.exec(sendCmd,{},(e,c)=>{
            //         if(e){
            //         }
            //         let sshResult:SSHResult={
            //             data: "",
            //             exitCode: 0
            //         }
            //         c.on("data",(data:Buffer)=>{
            //             let rel=data.toString()
            //             if(rel.indexOf("VisualSSHStatus:")>=0){
            //                 sshResult.exitCode=parseInt(rel.split("VisualSSHStatus:")[1].trim());
            //             }else{
            //                 sshResult.data=data.toString();
            //             }
            //         }).stderr.on("data",(data:Buffer)=>{
            //             sshResult.data=data.toString();
            //         })
            //         c.on('connect',console.error);
            //         c.on('close',()=>{
            //             resolve(sshResult);
            //         });
            //     })
            // })
            
        })
    }

    // run()
    
    /**
     * 发送一个回车
     * @returns 
     */
    async sendTest(){
        return await new Promise((resolve:(v:string)=>void,reject)=>{
            this.stream.write("\n");
            this.stream.on('data', (data: Buffer) => {
                let rel=data.toString("utf-8").trim();
                resolve(rel);
            });
        });
    }

    /**
     * 设置结束标志。没参数时是自动设置
     * --------
     * 对于这个双工流来说，没有办法保证发送和接收的数据是一一对应的，
     * 也没法保证每次接收的数据是完整的，所以第一步是测出数据结束标志物
     * @param title 
     */
    async setExpect (title?:string){
        if(title){
            return;
        }
        let test1=await this.sendTest();
        let test2=await this.sendTest();
        let test3=await this.sendTest();
        if((test1==test2 || test2==test3)&& test2!=""){
            let expects=test2.split("~");
            let expect=expects[0].trim();
            await this.checkCommandEnd();
            Status.setExpect(expect);
        }else{
            await this.setExpect();
        }
    }

    /**
     * 释放数据锁
     * @param lockId 
     * @returns 
     */
    private releaseDataLock(lockId:string){
        if(this.dataLock==lockId){
            this.dataLock="";
            return true;
        }else{
            throw new Error(`哭了呀，这不是你的锁啊！！！QAQ你的锁是${lockId}现在的锁是${this.dataLock}`)
        }
    }

    /**
     * 等待数据锁
     * @returns 
     */
     private async waitDataLockReleaseAndCreateLock(){
        // console.warn("等待数据锁");
        return await new Promise<string>((resolve, reject) => {
            const wait=()=>{
                if(!this.dataLock){
                    const lockId=Math.random()+"";
                    this.dataLock=lockId;
                    return resolve(lockId);
                }
                else{
                    setTimeout(wait,10);
                }
            }
            wait();
        })
    }   
}

interface ConnectConfig{
    username:string,
    password:string,
    host:string,
    port:number,
    privateKey:Buffer,
    // mainProgram:Program,
    parserCreator:(conn:Connect)=>IParser
}

interface ConnectInnerConfig{
    stream:ClientChannel,
    conn:Client,
    // mainProgram:Program,
    parserCreator:(conn:Connect)=>IParser
}

interface BaseConnectConfig{
    username:string,
    password:string,
    host:string,
    port:number,
    privateKey:Buffer,
}